package cn.elead.chaos.core.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;

/**
 * 字符串工具类
 *
 * @author luopeng
 */
public class StringUtils extends StrUtil {

	/**
	 * <p>
	 * 是否为CharSequence类型
	 * </p>
	 *
	 * @param object
	 * @return
	 */
	public static Boolean isCharSequence(Object object) {
		return Objects.isNull(object) ? false : isCharSequence(object.getClass());
	}

	/**
	 * <p>
	 * 是否为CharSequence类型
	 * </p>
	 *
	 * @param cls
	 * @return
	 */
	public static Boolean isCharSequence(Class<?> cls) {
		return cls != null && CharSequence.class.isAssignableFrom(cls);
	}

	/**
	 * 是否包含字符串
	 *
	 * @param str  验证字符串
	 * @param strs 字符串组
	 * @return 包含返回true
	 */
	public static boolean inStringIgnoreCase(String str, String... strs) {
		if (str != null && strs != null) {
			for (String s : strs) {
				if (str.equalsIgnoreCase(trim(s))) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空，则返回空字符串。 例如：HELLO_WORLD->HelloWorld
	 * 
	 * @param name 转换前的下划线大写方式命名的字符串
	 * @return 转换后的驼峰式命名的字符串
	 */
	public static String convertToCamelCase(String name) {
		StringBuilder result = new StringBuilder();
		// 快速检查
		if (name == null || name.isEmpty()) {
			// 没必要转换
			return "";
		} else if (!name.contains("_")) {
			// 不含下划线，仅将首字母大写
			return name.substring(0, 1).toUpperCase() + name.substring(1);
		}
		// 用下划线将原始字符串分割
		String[] camels = name.split("_");
		for (String camel : camels) {
			// 跳过原始字符串中开头、结尾的下换线或双重下划线
			if (camel.isEmpty()) {
				continue;
			}
			// 首字母大写
			result.append(camel.substring(0, 1).toUpperCase());
			result.append(camel.substring(1).toLowerCase());
		}
		return result.toString();
	}

	/**
	 * 下划线转驼峰命名
	 */
	public static String toUnderScoreCase(String str) {
		if (str == null) {
			return null;
		}
		StringBuilder sb = new StringBuilder();
		// 前置字符是否大写
		boolean preCharIsUpperCase;
		// 当前字符是否大写
		boolean curreCharIsUpperCase;
		// 下一字符是否大写
		boolean nexteCharIsUpperCase = true;
		for (int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);
			if (i > 0) {
				preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
			} else {
				preCharIsUpperCase = false;
			}

			curreCharIsUpperCase = Character.isUpperCase(c);

			if (i < (str.length() - 1)) {
				nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
			}

			if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) {
				sb.append(UNDERLINE);
			} else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) {
				sb.append(UNDERLINE);
			}
			sb.append(Character.toLowerCase(c));
		}
		return sb.toString();
	}

	/**
	 * <p>
	 * Replaces all occurrences of Strings within another String.
	 * </p>
	 *
	 * <p>
	 * A {@code null} reference passed to this method is a no-op, or if any "search string" or "string to replace" is
	 * null, that replace will be ignored. This will not repeat. For repeating replaces, call the overloaded method.
	 * </p>
	 *
	 * <pre>
	 *  StringUtils.replaceEach(null, *, *)        = null
	 *  StringUtils.replaceEach("", *, *)          = ""
	 *  StringUtils.replaceEach("aba", null, null) = "aba"
	 *  StringUtils.replaceEach("aba", new String[0], null) = "aba"
	 *  StringUtils.replaceEach("aba", null, new String[0]) = "aba"
	 *  StringUtils.replaceEach("aba", new String[]{"a"}, null)  = "aba"
	 *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""})  = "b"
	 *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"})  = "aba"
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"})  = "wcte"
	 *  (example of how it does not repeat)
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"})  = "dcte"
	 * </pre>
	 *
	 * @param text            text to search and replace in, no-op if null
	 * @param searchList      the Strings to search for, no-op if null
	 * @param replacementList the Strings to replace them with, no-op if null
	 * @return the text with any replacements processed, {@code null} if null String input
	 * @throws IllegalArgumentException if the lengths of the arrays are not the same (null is ok, and/or size 0)
	 * @since 2.4
	 */
	public static String replaceEach(final String text, final String[] searchList, final String[] replacementList) {
		return replaceEach(text, searchList, replacementList, false, 0);
	}

	/**
	 * <p>
	 * Replace all occurrences of Strings within another String. This is a private recursive helper method for
	 * {@link #replaceEachRepeatedly(String, String[], String[])} and {@link #replaceEach(String, String[], String[])}
	 * </p>
	 *
	 * <p>
	 * A {@code null} reference passed to this method is a no-op, or if any "search string" or "string to replace" is
	 * null, that replace will be ignored.
	 * </p>
	 *
	 * <pre>
	 *  StringUtils.replaceEach(null, *, *, *, *) = null
	 *  StringUtils.replaceEach("", *, *, *, *) = ""
	 *  StringUtils.replaceEach("aba", null, null, *, *) = "aba"
	 *  StringUtils.replaceEach("aba", new String[0], null, *, *) = "aba"
	 *  StringUtils.replaceEach("aba", null, new String[0], *, *) = "aba"
	 *  StringUtils.replaceEach("aba", new String[]{"a"}, null, *, *) = "aba"
	 *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *, >=0) = "b"
	 *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}, *, >=0) = "aba"
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}, *, >=0) = "wcte"
	 *  (example of how it repeats)
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false, >=0) = "dcte"
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true, >=2) = "tcte"
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, *, *) = IllegalStateException
	 * </pre>
	 *
	 * @param text            text to search and replace in, no-op if null
	 * @param searchList      the Strings to search for, no-op if null
	 * @param replacementList the Strings to replace them with, no-op if null
	 * @param repeat          if true, then replace repeatedly until there are no more possible replacements or
	 *                        timeToLive < 0
	 * @param timeToLive      if less than 0 then there is a circular reference and endless loop
	 * @return the text with any replacements processed, {@code null} if null String input
	 * @throws IllegalStateException    if the search is repeating and there is an endless loop due to outputs of one
	 *                                  being inputs to another
	 * @throws IllegalArgumentException if the lengths of the arrays are not the same (null is ok, and/or size 0)
	 * @since 2.4
	 */
	private static String replaceEach(final String text, final String[] searchList, final String[] replacementList,
			final boolean repeat, final int timeToLive) {

		// mchyzer Performance note: This creates very few new objects (one major goal)
		// let me know if there are performance requests, we can create a harness to measure

		if (text == null || text.isEmpty() || searchList == null || searchList.length == 0 || replacementList == null
				|| replacementList.length == 0) {
			return text;
		}

		// if recursing, this shouldn't be less than 0
		if (timeToLive < 0) {
			throw new IllegalStateException(
					"Aborting to protect against StackOverflowError - " + "output of one loop is the input of another");
		}

		final int searchLength = searchList.length;
		final int replacementLength = replacementList.length;

		// make sure lengths are ok, these need to be equal
		if (searchLength != replacementLength) {
			throw new IllegalArgumentException(
					"Search and Replace array lengths don't match: " + searchLength + " vs " + replacementLength);
		}

		// keep track of which still have matches
		final boolean[] noMoreMatchesForReplIndex = new boolean[searchLength];

		// index on index that the match was found
		int textIndex = -1;
		int replaceIndex = -1;
		int tempIndex = -1;

		// index of replace array that will replace the search string found
		// NOTE: logic duplicated below START
		for (int i = 0; i < searchLength; i++) {
			if (noMoreMatchesForReplIndex[i] || searchList[i] == null || searchList[i].isEmpty()
					|| replacementList[i] == null) {
				continue;
			}
			tempIndex = text.indexOf(searchList[i]);

			// see if we need to keep searching for this
			if (tempIndex == -1) {
				noMoreMatchesForReplIndex[i] = true;
			} else {
				if (textIndex == -1 || tempIndex < textIndex) {
					textIndex = tempIndex;
					replaceIndex = i;
				}
			}
		}
		// NOTE: logic mostly below END

		// no search strings found, we are done
		if (textIndex == -1) {
			return text;
		}

		int start = 0;

		// get a good guess on the size of the result buffer so it doesn't have to double if it goes over a bit
		int increase = 0;

		// count the replacement text elements that are larger than their corresponding text being replaced
		for (int i = 0; i < searchList.length; i++) {
			if (searchList[i] == null || replacementList[i] == null) {
				continue;
			}
			final int greater = replacementList[i].length() - searchList[i].length();
			if (greater > 0) {
				increase += 3 * greater; // assume 3 matches
			}
		}
		// have upper-bound at 20% increase, then let Java take over
		increase = Math.min(increase, text.length() / 5);

		final StringBuilder buf = new StringBuilder(text.length() + increase);

		while (textIndex != -1) {

			for (int i = start; i < textIndex; i++) {
				buf.append(text.charAt(i));
			}
			buf.append(replacementList[replaceIndex]);

			start = textIndex + searchList[replaceIndex].length();

			textIndex = -1;
			replaceIndex = -1;
			tempIndex = -1;
			// find the next earliest match
			// NOTE: logic mostly duplicated above START
			for (int i = 0; i < searchLength; i++) {
				if (noMoreMatchesForReplIndex[i] || searchList[i] == null || searchList[i].isEmpty()
						|| replacementList[i] == null) {
					continue;
				}
				tempIndex = text.indexOf(searchList[i], start);

				// see if we need to keep searching for this
				if (tempIndex == -1) {
					noMoreMatchesForReplIndex[i] = true;
				} else {
					if (textIndex == -1 || tempIndex < textIndex) {
						textIndex = tempIndex;
						replaceIndex = i;
					}
				}
			}
			// NOTE: logic duplicated above END

		}
		final int textLength = text.length();
		for (int i = start; i < textLength; i++) {
			buf.append(text.charAt(i));
		}
		final String result = buf.toString();
		if (!repeat) {
			return result;
		}

		return replaceEach(result, searchList, replacementList, repeat, timeToLive - 1);
	}

	/**
	 * 将map转化为以指定分隔符分隔的字符串
	 * 
	 * @param map
	 * @param separator
	 * @return
	 */
	public static String toString(Map<?, ?> map, String separator) {
		EasyBuffer sb = new EasyBuffer();
		for (Object key : map.keySet()) {
			sb.append(key).append("=").append(map.get(key)).append(separator);
		}
		return sb.clearEnd(separator.length());
	}

	/**
	 * 将map转化为以","作为分隔符的字符串
	 * 
	 * @param map
	 * @return
	 */
	public static String toString(Map<?, ?> map) {
		return toString(map, ",");
	}

	/**
	 * 将list转化为以指定分隔符分隔的字符串
	 * 
	 * @param es
	 * @param separator
	 * @return
	 */
	public static String toString(List<?> es, String separator) {
		EasyBuffer sb = new EasyBuffer();
		for (Object e : es) {
			if (ObjectUtil.isNotEmpty(e)) {
				sb.append(e).append(separator);
			}
		}
		return sb.clearEnd(separator.length());
	}

	/**
	 * 将list转化为以","作为分隔符的字符串
	 * 
	 * @param es
	 * @return
	 */
	public static String toString(List<?> es) {
		return toString(es, ",");
	}

	/**
	 * 将数组转化为指定分隔符分隔的字符串
	 * 
	 * @param es
	 * @param separator
	 * @return
	 */
	public static String toString(Object[] es, String separator) {
		return toString(new ArrayList<Object>(Arrays.asList(es)), separator);
	}

	/**
	 * 将数组转化为以","作为分隔符分隔的字符串
	 * 
	 * @param es
	 * @return
	 */
	public static String toString(Object[] es) {
		return toString(es, ",");
	}

	/**
	 * 默认值
	 * @param value
	 * @param defaultValue
	 * @return
	 */
	public static String defaultValue(String value, String defaultValue) {
		if(isBlank(value)) {
			return defaultValue;
		}
		return value;
	}
}